#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import json
import os
import time

from adds.comm.const import LogLevel
from common.common import execute_cmd, get_win_dir, output_execution_result_ex
from common.common_models import SubJobDetails, LogDetail
from exchange.commons.common import report_job_details_by_rpc, traverse_folder
from exchange.commons.const import SubJobStatusEnum, ParamConstant, ExchangeServiceStatus, ExchangeDatabaseCopyStatus, \
    ProgressConst, RepositoryDataTypeEnum
from exchange.commons.exchange_exception import ExchangeInternalException
from exchange import log
from exchange.restore.exchange_server_api.exchange_cmd_client import ExchangeCmdClient

# powershell路径
from exchange.restore.restore_param_parser import RestoreParamParser

PS_LOC = f"{get_win_dir()}/system32/WindowsPowerShell/v1.0/powershell.exe"
# 执行命令超时时间，30s
TIMEOUT = 30


class ExchangeRestoreBase:
    def __init__(self, p_id, job_id, sub_job_id, json_param):
        self._p_id = p_id
        self._job_id = job_id
        self._sub_job_id = sub_job_id
        self._json_param = json_param
        self._exchange_cmd_client = ExchangeCmdClient(self._p_id)
        self.cache_path = ""
        self._progress = ProgressConst.MIN_PROGRESS

    def get_cache_path(self):
        """
        获取cache仓路径
        :return: cache仓路径
        """
        copies = self._json_param.get("job", {}).get("copies", [])
        for copy in copies:
            repositories_json = copy.get("repositories", [])
            for jsn_mbr in repositories_json:
                if jsn_mbr.get("repositoryType", "") \
                        == 2:
                    self.cache_path = jsn_mbr.get("path", [""])[0]
                    # 适配复制恢复，获取到cache仓路径后再停止获取
                if self.cache_path:
                    break

    def write_progress_to_file(self, task_status, progress, log_detail, progress_type):
        if not self.cache_path:
            self.get_cache_path()
        output = SubJobDetails(taskId=self._job_id, subTaskId=self._sub_job_id, progress=int(progress),
                               logDetail=list(), taskStatus=task_status)
        if log_detail:
            output.log_detail.append(log_detail)
        file_path = os.path.join(self.cache_path, progress_type)
        output_execution_result_ex(file_path, output.dict(by_alias=True))

    def report_progress_comm(self, file_name: str = None):
        self.get_cache_path()
        if not self.cache_path:
            log.error("Get_cache_path_filed.")
            return False
        # 默认查询common进度文件
        if not file_name:
            file_name = "common"
        file_path_comm = os.path.join(self.cache_path, file_name)
        try:
            with open(file_path_comm, 'r') as f:
                json_str = json.loads(f.read())
        except Exception:
            json_str = SubJobDetails(taskId=self._job_id, subTaskId=self._sub_job_id, progress=5,
                                     logDetail=list(), taskStatus=SubJobStatusEnum.FAILED.value).dict(by_alias=True)
        file_path = os.path.join(ParamConstant.RESULT_PATH, f"result{self._p_id}")
        output_execution_result_ex(file_path, json_str)
        return True

    def _check_exchange_online_status(self):
        """
        检查exchange server运行状态
        :return: true,false
        """
        log.info('Start check exchange online status')
        # 校验exchange server在线状态结果
        if ExchangeServiceStatus.RUNNING == self._exchange_cmd_client.get_exchange_online_status():
            log.info('Exchange is online.')
            return True
        log.error('Exchange is offline.')
        return False

    def _check_exchange_version_consistency(self, expect_version: str):
        """
        检查exchange server版本一致性
        :param expect_version: 期望版本
        :return: true,false
        """
        log.info('Start check exchange version consistency')
        # 校验exchange server版本(比较大版本例如15.1)
        copy_version = self._exchange_cmd_client.get_exchange_version()[0:4]
        log.info(f"exception version:{expect_version}, copy version:{copy_version}")
        if expect_version[0:4] == copy_version[0:4]:
            log.info('Exchange version is consistent.')
            return True, copy_version
        log.error('Exchange version not consistent.')
        return False, copy_version

    def _check_exchange_database_exist(self, database_name: str):
        """
        检查exchange database是否存在
        :param database_name: 数据库名称
        :return: true,false
        """
        log.info(f'Start check exchange database:{database_name} exist')
        # 校验exchange guid
        try:
            if self._exchange_cmd_client.get_exchange_database_guid(database_name) is not None:
                log.info(f'Exchange database:{database_name} is exist.')
                return True
            log.error(f'Exchange database:{database_name} is not exist.')
            return False
        except ExchangeInternalException:
            log.error(f'Exchange database:{database_name} is not exist.')
            return False

    def _set_exchange_database_allow_restore(self, database_name: str):
        """
        设置数据库属性允许覆盖恢复
        :param database_name: 数据库名称
        :return:
        """
        log.info(f'Set exchange database:{database_name} allow restore')
        self._exchange_cmd_client.set_exchange_database_allow_file_restore(database_name)

    def _check_exchange_database_server_name(self, database_name: str):
        """
        检查exchange database 的服务名是否存在
        :param database_name: 数据库名称
        :return: server_name,None
        """
        log.info(f'Start check exchange database :{database_name}  server exist')
        try:
            server_name = self._exchange_cmd_client.get_exchange_database_server_name(database_name)
            if server_name is not None:
                log.info(f'Exchange database :{database_name} server is exist.')
                return server_name
            log.error(f'Exchange database :{database_name} server is not exist.')
            return None
        except ExchangeInternalException:
            log.error(f'Exchange database :{database_name} server is not exist.')
            return None

    def _get_exchange_database_server_name(self, database_name: str):
        """
        获取exchange server database所属服务名称
        :param database_name: 数据库名称
        :return: 服务名
        """
        server_name = self._exchange_cmd_client.get_exchange_database_server_name(database_name)
        log.info(f'Get exchange database:{database_name} server:{server_name}')
        return server_name

    def _get_exchange_database_copies(self, copy_name: str):
        """
        获取数据副本
        :param copy_name: 数据库名称
        :return:
        """
        log.info(f'Get exchange database copies')
        copies = self._exchange_cmd_client.get_exchange_database_copies(copy_name)
        return copies

    def _suspend_passive_copy(self, database_name: str):
        """
        暂停数据库被动副本复制
        :param database_name: 数据库名称
        :return:
        """
        log.info(f'Suspend exchange passive copy database:{database_name}')
        # 查询所有复制副本
        copies = self._get_exchange_database_copies(database_name)
        suspend_copy_list = []
        for copy in copies:
            # 暂停healthy状态副本的复制和激活,mounted为活动副本，
            # Suspended为暂停的副本.
            if ExchangeDatabaseCopyStatus.Healthy == copy.get("Status").get("value"):
                self._exchange_cmd_client.suspend_passive_copy(copy.get("Name"))
                suspend_copy_list.append(copy.get("Name"))
        suspend_status_updated = self._wait_update_passive_copy_finish(database_name, suspend_copy_list,
                                                                       [ExchangeDatabaseCopyStatus.Suspended], 5 * 60)
        if not suspend_status_updated:
            raise ExchangeInternalException("Passive copies not all suspended")

    def _wait_update_passive_copy_finish(self, database_name: str, update_copies: list, expect_status: list,
                                         wait_time_out: int):
        """
        等待被动副本更新
        停止被动副本复制->状态为healthy的时候一直等待
        更新被动副本复制->状态为
        :param database_name: 数据库名称
        :param update_copies: 待更新副本列表
        :param expect_status: 期望状态
        :param wait_time_out: 等待超时时间s
        :return: 是否更新成功
        """
        log.info(f"Wait update copies:{update_copies} status")
        is_update_success = False
        start_time = time.time()
        while not is_update_success and time.time() - start_time < wait_time_out:
            log.debug(
                f"Passive copies except status:{expect_status} wait: {time.time() - start_time}s!")
            copies = self._get_exchange_database_copies(database_name)
            updated_copies = [copy for copy in copies if copy.get("Name") in update_copies]
            is_update_success = all(copy.get("Status").get("value") in expect_status for copy in updated_copies)
            time.sleep(10)
        return is_update_success

    def _update_passive_copy_seed(self, database_name: str):
        """
        恢复被动副本复制
        :param database_name: 数据库名称
        :return:
        """
        # 查询所有复制副本
        copies = self._get_exchange_database_copies(database_name)
        resume_copy_list = []
        for copy in copies:
            # 恢复Suspended为暂停的副本, healthy状态副本的复制和激活, mounted为活动副本.
            if ExchangeDatabaseCopyStatus.Suspended == copy.get("Status").get("value"):
                log.info(f'update exchange passive copy database seed:{copy.get("Name")}')
                self._exchange_cmd_client.update_passive_copy_seed(copy.get("Name"))
                resume_copy_list.append(copy.get("Name"))
        self._wait_update_passive_copy_finish(database_name, resume_copy_list,
                                              [ExchangeDatabaseCopyStatus.Healthy], 2 * 60 * 60)

    def _mount_exchange_database(self, database_name: str):
        """
        挂载exchange数据库
        :param database_name: 数据库名称
        :return:
        """
        log.info(f'Mount exchange database:{database_name}')
        self._exchange_cmd_client.mount_exchange_database(database_name)

    def _dismount_exchange_database(self, database_name: str):
        """
        卸载exchange数据库
        :param database_name: 数据库名称
        :return:
        """
        log.info(f'Dismount exchange database:{database_name}')
        self._exchange_cmd_client.dismount_exchange_database(database_name)

    def _check_exchange_database_dismounted_status(self, database_name: str):
        """
        检查活动数据库副本卸载状态
        :param database_name:
        :return:
        """
        log.info(f'check exchange database :{database_name} dismount status')
        # 生成指定副本名称（database_name\server_name）
        code, hostname, err = execute_cmd([PS_LOC, "hostname"], timeout=TIMEOUT, cmd_array_flag=True)
        hostname = hostname.strip()
        copy_name = database_name + '\\' + hostname
        log.info(f'check exchange database copy:{copy_name} dismount status')
        # 查询数据库对应副本
        copies = self._get_exchange_database_copies(copy_name)
        if copies.__len__() != 1:
            log.error(f"can not find or multi copy:{copy_name}")
            return False
        # 判断活动副本状态是否为Dismounted
        if ExchangeDatabaseCopyStatus.DISMOUNTED != copies[0].get("Status").get("value"):
            log.error("exchange database still mounted")
            return False
        log.info(f'exchange database :{database_name} is dismounted')
        return True

    def _check_exchange_database_mounted_status(self, database_name: str):
        """
        检查活动数据库副本装载状态
        :param database_name:
        :return:
        """
        log.info(f'check exchange database :{database_name} dismount status')
        # 生成指定副本名称（database_name\server_name）
        code, hostname, err = execute_cmd([PS_LOC, "hostname"], timeout=TIMEOUT, cmd_array_flag=True)
        hostname = hostname.strip()
        copy_name = database_name + '\\' + hostname
        log.info(f'check exchange database copy:{copy_name} dismount status')
        # 查询数据库对应副本
        copies = self._get_exchange_database_copies(copy_name)
        if copies.__len__() != 1:
            log.error(f"can not find or multi copy:{copy_name}")
            return False
        # 判断活动副本状态是否为Mounted
        if ExchangeDatabaseCopyStatus.MOUNTED != copies[0].get("Status").get("value"):
            log.error("exchange database still dismounted")
            return False
        log.info(f'exchange database :{database_name} is mounted')
        return True

    def _get_edb_and_log_path(self, database_name: str):
        """
        获取exchange 数据库edb文件和log
        :param database_name:
        :return:
        """
        edb_path, log_path = self._exchange_cmd_client.get_exchange_database_edb_and_log_path(database_name)
        log.info(f'Get exchange edb path:{edb_path} and log path:{log_path}')
        return edb_path, log_path

    def _get_exchange_database_guid(self, database_name: str):
        """
        获取exchange 数据库guid
        :param database_name:
        :return:
        """
        guid = self._exchange_cmd_client.get_exchange_database_guid(database_name)
        log.info(f'Get exchange guid :{guid}')
        return guid

    def _get_exchange_database_log_file_prefix(self, database_name: str):
        """
        获取exchange 数据库log文件前缀
        :param database_name:
        :return:
        """
        log_file_prefix = self._exchange_cmd_client.get_exchange_database_log_prefix(database_name)
        log.info(f'Get exchange log file prefix :{log_file_prefix}')
        return log_file_prefix

    def _create_rdb_database(self, database_name, edb_file_path, log_folder_path):
        """
        创建RDB数据库
        :param database_name: 数据库名称
        :param edb_file_path: edb文件路径
        :param log_folder_path: log文件夹路径
        """
        self._exchange_cmd_client.create_rdb_database(database_name, edb_file_path, log_folder_path)

    def _create_exchange_database(self, database_name, server_name, edb_path, log_path):
        """
        创建活动数据库
        :param database_name: 数据库名称
        :param edb_path: edb文件路径
        :param log_path: log文件夹路径
        """
        try:
            log.info(f"start create exchange database, database_name:{database_name}.")
            self._exchange_cmd_client.create_exchange_database(database_name, server_name, edb_path, log_path)
            return True
        except ExchangeInternalException:
            return False

    def _restore_mailbox(self, request_name, database_name, source_mailbox, target_mailbox):
        """
        从rdb数据库恢复邮箱
        :param request_name: 请求名
        :param database_name:数据库名
        :param source_mailbox: 原邮箱
        :param target_mailbox: 目标邮箱
        :return:request_id
        """
        return self._exchange_cmd_client.restore_mailbox(request_name, database_name, source_mailbox, target_mailbox)

    def _get_mailbox_restore_request_status(self, request_name):
        """
        查询邮箱恢复请求状态
        :param request_name: 请求名
        :return: 状态
        """
        return self._exchange_cmd_client.get_mailbox_restore_request_status(request_name)

    def _remove_mailbox_restore_request(self, request_id):
        """
        删除邮箱恢复请求
        :param request_id: 请求id
        """
        self._exchange_cmd_client.remove_mailbox_restore_request(request_id)

    def _remove_database(self, database_name):
        """
        删除数据库
        :param database_name: 数据库名
        :return: 删除成功
        """
        return self._exchange_cmd_client.remove_database(database_name)

    def _get_database_in_server(self):
        """
        查询节点的数据库
        :return: 数据库
        """
        return self._exchange_cmd_client.get_database_in_server()

    def _get_database_status(self, db_name):
        """
        查询数据库状态
        :param db_name: 数据库名
        :return: 状态
        """
        return self._exchange_cmd_client.get_database_status(db_name)

    def _check_exchange_mailbox_exist(self, mailbox_name):
        """
        检测邮箱是否存在
        :param mailbox_name: 邮箱name
        :return: True 运行成功, False 运行失败
        """
        return self._exchange_cmd_client.check_exchange_mailbox_exist(mailbox_name)

    def _get_idle_node_server(self):
        all_database_copies = self._exchange_cmd_client.get_all_database_copies()
        # 筛选在nodes中的副本
        nodes_list = RestoreParamParser.get_restore_nodes_list(self._json_param)
        server_list = [node["name"].upper() for node in nodes_list]
        log.info(f"server list:{server_list}")
        copies = []
        if all_database_copies is None:
            return server_list
        if not isinstance(all_database_copies, list):
            database_all_copies = [all_database_copies]
        else:
            database_all_copies = all_database_copies
        for database_all_copy in database_all_copies:
            database_copies = database_all_copy.get('DatabaseCopies')
            server_name = database_all_copy.get('Server').get('Name')
            if server_name.upper() not in server_list:
                continue
            for copy in database_copies:
                copies.append(copy)
        count_dict = dict()
        for item in copies:
            server_name = item.get("HostServerName") if isinstance(item, dict) else item.split("\\")[-1]
            if server_name in count_dict:
                count_dict[server_name] += 1
            else:
                count_dict[server_name] = 1
        idle_node_server = sorted(count_dict)[-1]
        log.info(f"current idle node server name:{idle_node_server}, count_dict:{count_dict}")
        return idle_node_server

    def _report_failed_progress(self, label: str, params: list, progress: int):
        self._progress = progress
        log_detail = LogDetail(logInfo=label,
                               logInfoParam=params,
                               logLevel=LogLevel.ERROR.value)
        self._report_progress(SubJobStatusEnum.FAILED.value, progress, [log_detail])

    def _report_running_progress(self, label: str, params: list, progress: int):
        self._progress = progress
        log_detail = LogDetail(logInfo=label,
                               logInfoParam=params,
                               logLevel=LogLevel.INFO.value)
        self._report_progress(SubJobStatusEnum.RUNNING.value, progress, [log_detail])

    def _report_completed_progress(self, label: str, params: list, progress: int):
        self._progress = progress
        log_detail = LogDetail(logInfo=label,
                               logInfoParam=params,
                               logLevel=LogLevel.INFO.value)
        self._report_progress(SubJobStatusEnum.COMPLETED.value, progress, [log_detail])

    def _report_progress(self, status: int, progress: int, log_details: list):
        log.info("Start report to pm")
        repositories = RestoreParamParser.get_exchange_database_copy_repo(self._json_param)
        data_path = ""
        for repository in repositories:
            if repository.get("repositoryType") == RepositoryDataTypeEnum.DATA_REPOSITORY.value:
                data_path = repository.get("path", [""])[0]
        data_total_size = traverse_folder(data_path)

        self._progress = progress
        output = SubJobDetails(taskId=self._job_id, subTaskId=self._sub_job_id, progress=progress,
                               logDetail=log_details, taskStatus=status, dataSize=int(data_total_size / 1024)).dict(
            by_alias=True)
        report_job_details_by_rpc(self._job_id, self._p_id, self._sub_job_id, output)
        log.info("Start report to pm end")
